#!/usr/bin/python
#pyhk.py
#
# Copyright (C) 2011 Michael Schurpf
# E-mail: michaelschuerpf AT gmail DOT com 
#
# Extends pyhook to have normal hotkey functionality like autohotkey (AHK) scripts.
# Hotkeys have to be entered in lists! Even if single items.
# Hotkeys can be entered in ID list or human readble.
# Example: human readble: ['Lcontrol','7'] for CTRL 7.
# See createKeyLookup function for all labels,
#
# Scripts maintained at http://www.swisstard.com
# Comments, suggestions and bug reports welcome.
#
# Released subject to the GNU Public License
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# In addition to the permissions in the GNU General Public License, the
# authors give you unlimited permission to link or embed the compiled
# version of this file into combinations with other programs, and to
# distribute those combinations without any restriction coming from the
# use of this file. (The General Public License restrictions do apply in
# other respects; for example, they cover modification of the file, and
# distribution when not linked into a combine executable.)
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

import pythoncom, pyHook, ctypes

class pyhk:
    """Hotkey class extending pyhook"""

    def __init__(self , EventHistory = 5):
        #initiate internal hotkey list
        self.KeyDownID = []
        self.KeyDown = []
        self.EventHistory = EventHistory
        #initiate user hotkey list
        self.UserHKF = []
        #End hotkey = CTRL Shift Q
        self.EndHotkey = [162,160,81]

        #create Lookup for event keys and ids
        self.ID2Key, self.Key2ID = self.createKeyLookup()
        
        # create a hook manager
        self.hm = pyHook.HookManager()
        # watch for all mouse events
        self.hm.KeyDown = self.OnKeyDown
        self.hm.KeyUp = self.OnKeyUp
        # set the hook
        self.hm.HookKeyboard()

    def start(self):
        """Start pyhk to check for hotkeys"""
        pythoncom.PumpMessages()

    def end(self):
        """End pyhk to check for hotkeys"""
        ctypes.windll.user32.PostQuitMessage(0)

#--------------------------------------------------------

    def isIDHotkey(self,hotkey):
        """Test if hotkey is coded in IDs"""
        try:
            [self.ID2Key[key] for key in hotkey]
        except:
            return False
        return True

    def isHumanHotkey(self,hotkey):
        """Test if hotkey is coded human readable. Ex ALT F2"""
        try:
            [self.Key2ID[key] for key in hotkey]
        except:
            return False
        return True

    def hotkey2ID(self,hotkey):
        """Converts human readable hotkeys to IDs"""
        if self.isHumanHotkey(hotkey):
            return [self.Key2ID[key] for key in hotkey]
        else:
            raise Exception("Invalid Hotkey")

            
    def addHotkey(self,hotkey,fhot):
        """Add hotkeys with according function"""
        if self.isIDHotkey(hotkey):
            self.UserHKF.append([hotkey,fhot])
        else:
            self.UserHKF.append([self.hotkey2ID(hotkey),fhot])
            
    def removeHotkey(self,hotkey):
        """Remove hotkeys and corresponding function"""
        if self.isIDHotkey(hotkey):            
            tempUserHKF = [[hotk, fun] for hotk, fun in self.UserHKF if not(hotk == hotkey)]
        else:
            tempUserHKF = [[hotk, fun] for hotk, fun in self.UserHKF if not(hotk == self.hotkey2ID(hotkey))]
        self.UserHKF = tempUserHKF[:]

    def setEndHotkey(self, hotkey):
        """Add exit hotkeys"""
        if self.isIDHotkey(hotkey):
            self.EndHotkey = hotkey[:]
        else:
            self.EndHotkey = self.hotkey2ID(hotkey)[:]  
   
#--------------------------------------------------------

    def isHotkey(self,hotkey):
        """Check if hotkey is pressed down
            Hotkey is given as KeyID"""

        try:            
            #make sure exact hotkey is pressed
            if not(len(hotkey) == len(self.KeyDownID)):
                return  False
            for hotk in hotkey:
                if not(hotk in self.KeyDownID):
                    return False
        except:
            return False
        
        return True

    def OnKeyDown(self,event):

        #make sure key only gets presse once
        if not(event.KeyID in self.KeyDownID):
            self.KeyDownID.append(event.KeyID)
            self.KeyDown.insert(0,event)

            #Add user hotkeys and functions
            for hk, fun in self.UserHKF:
                if self.isHotkey(hk):
                    fun()
            
            #End loop
            if self.isHotkey(self.EndHotkey):
                self.end()

        return True

     
    def OnKeyUp(self,event):
        try:
            self.KeyDownID.remove(event.KeyID)
            if len(self.KeyDown) > self.EventHistory:      
                self.KeyDown.pop()
        except:
            pass
        return True

#--------------------------------------------------------

    def createKeyLookup(self):
        """Creates Key look up dictionaries, change names as you please"""
        ID2Key = {   8: 'Back',
                     9: 'Tab',
                     13: 'Return',
                     20: 'Capital',
                     27: 'Escape',
                     32: 'Space',
                     33: 'Prior',
                     34: 'Next',
                     35: 'End',
                     36: 'Home',
                     37: 'Left',
                     38: 'Up',
                     39: 'Right',
                     40: 'Down',
                     44: 'Snapshot',
                     46: 'Delete',
                     48: '0',
                     49: '1',
                     50: '2',
                     51: '3',
                     52: '4',
                     53: '5',
                     54: '6',
                     55: '7',
                     56: '8',
                     57: '9',
                     65: 'A',
                     66: 'B',
                     67: 'C',
                     68: 'D',
                     69: 'E',
                     70: 'F',
                     71: 'G',
                     72: 'H',
                     73: 'I',
                     74: 'J',
                     75: 'K',
                     76: 'L',
                     77: 'M',
                     78: 'N',
                     79: 'O',
                     80: 'P',
                     81: 'Q',
                     82: 'R',
                     83: 'S',
                     84: 'T',
                     85: 'U',
                     86: 'V',
                     87: 'W',
                     88: 'X',
                     89: 'Y',
                     90: 'Z',
                     91: 'Lwin',
                     96: 'Numpad0',
                     97: 'Numpad1',
                     98: 'Numpad2',
                     99: 'Numpad3',
                     100: 'Numpad4',
                     101: 'Numpad5',
                     102: 'Numpad6',
                     103: 'Numpad7',
                     104: 'Numpad8',
                     105: 'Numpad9',
                     106: 'Multiply',
                     107: 'Add',
                     109: 'Subtract',
                     110: 'Decimal',
                     111: 'Divide',
                     112: 'F1',
                     113: 'F2',
                     114: 'F3',
                     115: 'F4',
                     116: 'F5',
                     117: 'F6',
                     118: 'F7',
                     119: 'F8',
                     120: 'F9',
                     121: 'F10',
                     122: 'F11',
                     123: 'F12',
                     144: 'Numlock',
                     160: 'Lshift',
                     161: 'Rshift',
                     162: 'Lcontrol',
                     163: 'Rcontrol',
                     164: 'Lmenu',
                     165: 'Rmenu',
                     186: 'Oem_1',
                     187: 'Oem_Plus',
                     188: 'Oem_Comma',
                     189: 'Oem_Minus',
                     190: 'Oem_Period',
                     191: 'Oem_2',
                     192: 'Oem_3',
                     219: 'Oem_4',
                     220: 'Oem_5',
                     221: 'Oem_6',
                     222: 'Oem_7'}
        Key2ID = dict(map(lambda x,y: (x,y),ID2Key.values(),ID2Key.keys()))
        return ID2Key, Key2ID

if __name__ == '__main__':

    #define test function ALT 1
    def funk1():
        print "Hotkey pressed: Lcontrol 7"    

    #create pyhk class instance    
    hot = pyhk()

    #add hotkey
    hot.addHotkey(['Lcontrol','7'],funk1)
    
    #start looking for hotkey
    hot.start()

    #add several hotkeys to the same function
    hot.addHotkey(['Lcontrol','8'],funk1)

    #remove hotkey Lcontrol 7
    hot.removeHotkey(['Lcontrol','7'])

    #change exit hotkey,
    #default is CTRL Shift Q and will be used if no EndHotkey is set
    hot.setEndHotkey(['Lshift','Q'])

    #start looking for hotkey
    hot.start()

##    #hotkeys also work with IDs
##
##    #test ctrl = 162 shift = 160 3 = 51
##    def funk2():
##        print "CTRL Shift 3"
##
##    hot.addHotkey([162,160,51],funk2)
##
##    #test ctrl = 162 shift = 160 3 = 51
##    def funk3():
##        print "CTRL 7 ", str([(key.Key,key.KeyID) for key in hot.KeyDown])
##        
##    hot.addHotkey(['Lcontrol','7'],funk3)
##
##    print "starting pyhk phase"
##   
##    hot.start()

##    #change end hotkey to 6
##    hot.setEndHotkey([54])
##
##    #remove hotkey ALT F2
##    hot.removeHotkey([164,113])
##
##    print "reentering pyhk phase"
##
##    hot.start()
##
##    def funk3():
##        
##        hot.end()
##
##    hot.addHotkey([51],funk3)
##
##    print "special exit function, rereentering pyhk phase"
##
##    hot.start()


